﻿using Pvf.Core.Contents;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace Pvf.Core
{
    /// <summary>
    /// Pvf环境
    /// </summary>
    public sealed class PvfEnv : IPvfEnv
    {
        /// <summary>
        /// 
        /// </summary>
        public string UUID { get; set; }

        /// <summary>
        /// 
        /// </summary>
        public int Version { get; set; }

        /// <summary>
        /// 
        /// </summary>
        public uint IndexVector { get; set; }

        public IDictionary<int, string> StringTable => _stringTable?.GetContent<PvfStringTableContent>().Data;

        public IDictionary<string, string> NStringTable => _nStringTable?.GetContent<PvfNStringContent>().Data;

        public IFileExplorer FileExplorer { get; private set; }

        public ILogger Logger { get; } = null;

        public uint NStringEntryKey { get; set; }

        public string Lang { get; set; }

        public string NLang { get; set; }

        public IDictionary<string, HashSet<int>> ReverseStringTable => _reverseStringTables;


        private PvfIndex _nStringTable;
        private PvfIndex _stringTable;

        private readonly string _fileName;
        private bool _disposedValue;
        private FileStream _fileStream;
        private BinaryReader _fileReader;

        private readonly Dictionary<Type, IPvfModule> _registeredModules;
        private readonly Dictionary<string, HashSet<int>> _reverseStringTables;

        /// <summary>
        /// 开发模式下string.bin输出文件
        /// </summary>
        public static string StringTableBinOutputFile { get; set; }
        
        /// <summary>
        /// 开发模式
        /// </summary>
        public static bool IsDevelopmentMode { get; set; }

        public ScriptVM VM {get;}

        /// <summary>
        /// PVF环境
        /// </summary>
        /// <param name="fileName">PVF文件</param>
        /// <param name="lang">默认语音</param>
        /// <param name="nlang"></param>
        /// <param name="logger">日志接口</param>
        public PvfEnv(string fileName, string lang, string nlang, ILogger logger = null)
        {
            VM = new ScriptVM();
            
            IndexVector = 0x81A79011;
            NStringEntryKey = 53424;

            Lang = lang;
            NLang = nlang;

            _fileName = fileName;

            FileExplorer = new PvfFileExplorer(this);

            _registeredModules = new Dictionary<Type, IPvfModule>();
            _reverseStringTables = new Dictionary<string, HashSet<int>>(StringComparer.CurrentCultureIgnoreCase);

            Logger = logger;
        }

        public PvfIndex FindIndex(string fileName)
        {
            var pvfFile = FileExplorer.Find<PvfFile>(fileName, false);
            return pvfFile?.Data;
        }

        private void UnpackStringTable()
        {
            _stringTable = FindIndex("stringtable.bin");

            var content = _stringTable.GetContent<PvfStringTableContent>(Encoding.GetEncoding("BIG5"));

            StringBuilder sb = new StringBuilder();
            foreach (var item in content.Data)
            {
                if (IsDevelopmentMode && !string.IsNullOrWhiteSpace(StringTableBinOutputFile))
                {
                    sb.AppendLine($"{item.Key}\t{item.Value}");
                }
                if (!_reverseStringTables.TryGetValue(item.Value, out var foundLst))
                {
                    foundLst = new HashSet<int>();
                    _reverseStringTables.Add(item.Value, foundLst);
                }

                foundLst.Add(item.Key);
            }
            if (IsDevelopmentMode && !string.IsNullOrWhiteSpace(StringTableBinOutputFile))
            {
                File.WriteAllText(StringTableBinOutputFile, sb.ToString());
                Logger?.LogInfo("Pvf", $"stringtable.bin已经保存到{StringTableBinOutputFile}");
            }
            Logger?.LogInfo("Pvf", "stringtable.bin初始化");
        }

        private void UnpackNStringLst()
        {
            _nStringTable = FindIndex("n_string.lst");

            _nStringTable.LoadContent<PvfNStringContent>();

            Logger?.LogInfo("Pvf", "n_string.bin初始化");
        }

        private Task<bool> LoadAsync(BinaryReader reader)
        {
            return Task.Run(() =>
            {
                var sw = new System.Diagnostics.Stopwatch();
                try
                {
                    sw.Start();
                    Logger?.LogInfo("Pvf", $"加载文件[{_fileName}]");

                    var encoding0X3B5 = Encoding.GetEncoding(0x3b5);

                    var guidLen = reader.ReadInt32();

                    var guidBytes = reader.ReadBytes(guidLen);

                    UUID = encoding0X3B5.GetString(guidBytes);

                    Version = reader.ReadInt32();

                    var indexDataLength = reader.ReadInt32();

                    var indexDataCrc32 = reader.ReadUInt32();

                    var filesCount = reader.ReadInt32();

                    var indexData = UnpackContent(-1, indexDataLength, indexDataCrc32);

                    var headerSizeof = sizeof(int) + guidBytes.Length + sizeof(int) + sizeof(int) + sizeof(int) +
                                       sizeof(int);

                    using (var ms = new MemoryStream(indexData))
                    {
                        using (var iReader = new BinaryReader(ms))
                        {
                            for (var i = 0; i < filesCount; i++)
                            {
                                var pvfIndex = new PvfIndex
                                {
                                    Env = this,
                                    FileId = iReader.ReadUInt32()
                                };
                                var fileNameLength = iReader.ReadInt32();

                                pvfIndex.FileName = encoding0X3B5.GetString(iReader.ReadBytes(fileNameLength))
                                    .TrimEnd(new char[1]);
                                pvfIndex.FileLength = iReader.ReadInt32();
                                pvfIndex.ComputedFileLength = (int)((pvfIndex.FileLength + 3L) & 4294967292L);
                                pvfIndex.FileCrc32 = iReader.ReadUInt32();
                                pvfIndex.RelativeOffset = iReader.ReadInt32();
                                pvfIndex.AbsoluteOffset = headerSizeof + indexDataLength + pvfIndex.RelativeOffset;

                                var fileName = Path.GetFileName(pvfIndex.FileName);

                                var pvfFile = new PvfFile(fileName, this, pvfIndex);

                                FileExplorer.Add(pvfIndex.FileName, pvfFile);
                            }

                            UnpackStringTable();
                            UnpackNStringLst();

                            sw.Stop();
                            Logger?.LogInfo("Pvf", $"解析Pvf索引完毕,共{filesCount}个文件...用时{sw.Elapsed.TotalMilliseconds}ms");
                            return true;
                        }
                    }
                }
                catch (Exception ex)
                {
                    sw.Stop();

                    Logger?.LogException("Pvf", ex);
                }

                return false;
            });
        }

        public Task<bool> LoadAsync()
        {
            if (string.IsNullOrEmpty(_fileName))
            {
                return Task.FromResult(false);
            }

            _fileStream = File.OpenRead(_fileName);
            _fileReader = new BinaryReader(_fileStream);

            return LoadAsync(_fileReader);
        }

        private void Dispose(bool disposing)
        {
            if (!_disposedValue)
            {
                if (disposing)
                {
                    // TODO: 释放托管状态(托管对象)

                    _fileReader?.Dispose();
                    _fileStream?.Dispose();
                    _fileStream = null;
                    _fileReader = null;
                }

                // TODO: 释放未托管的资源(未托管的对象)并重写终结器
                // TODO: 将大型字段设置为 null
                _disposedValue = true;
            }
        }

        // TODO: 仅当“Dispose(bool disposing)”拥有用于释放未托管资源的代码时才替代终结器
        ~PvfEnv()
        {
            // 不要更改此代码。请将清理代码放入“Dispose(bool disposing)”方法中
            Dispose(disposing: false);
        }

        public void Dispose()
        {
            // 不要更改此代码。请将清理代码放入“Dispose(bool disposing)”方法中
            Dispose(disposing: true);
            GC.SuppressFinalize(this);
        }

        private static uint ROR4(uint uint_0, int int_1)
        {
            return (uint_0 >> int_1) | (uint_0 << (32 - int_1));
        }

        private void Decode(byte[] decode, uint crc32)
        {
            var tIndex = 0;
            while (tIndex < decode.Length)
            {
                var oldValue = BitConverter.ToUInt32(decode, tIndex);

                Buffer.BlockCopy(BitConverter.GetBytes(ROR4(oldValue ^ IndexVector ^ crc32, 6)), 0, decode, tIndex,
                    sizeof(uint));
                tIndex += sizeof(uint);
            }
        }

        public byte[] UnpackContent(int pos, int len, uint crc32)
        {
            if (_fileReader == null)
            {
                return null;
            }

            if (pos != -1)
            {
                _fileReader.BaseStream.Position = pos;
            }

            var indexData = _fileReader.ReadBytes(len);

            Decode(indexData, crc32);

            return indexData;
        }

        public TModule GetModule<TModule>() where TModule : class, IPvfModule
        {
            LoadModule(typeof(TModule), out var pvfModule);

            return pvfModule as TModule;
        }

        private void LoadModule(Type modType, out IPvfModule pvfModule)
        {
            if (!_registeredModules.TryGetValue(modType, out pvfModule))
            {
                var sw = new System.Diagnostics.Stopwatch();
                sw.Start();

                var modName = modType.GetCustomAttribute<ModuleEntryAttribute>();

                pvfModule = Activator.CreateInstance(modType, this, modName.Name, null) as IPvfModule;

                if (pvfModule is IPvfModuleInitializer initializer)
                {
                    initializer.Init();
                }

                sw.Stop();

                _registeredModules.Add(modType, pvfModule);

                Logger?.LogWarning("Pvf",
                    $">>>加载模块[{modName.Name}],共{pvfModule.Objects?.Count ?? 0}个预加载对象,{pvfModule.Strings?.Count ?? 0}条本地化数据...用时{sw.Elapsed.TotalMilliseconds}ms");
            }
        }

        private void BuildPvfObject(Type objType)
        {
            if (!typeof(PvfObjectBase).IsAssignableFrom(objType))
            {
                throw new NotSupportedException($"对象{objType.Name}必须继承自PvfObject");
            }

            var sw = new System.Diagnostics.Stopwatch();
            sw.Start();
            PvfObjectBuilder.Build(objType, this);
            sw.Stop();

            Logger?.LogWarning("Parser", $"Build Object Layout::{objType.Name} 使用{sw.Elapsed.Milliseconds}ms");
        }

        private void BuildObjectLayouts(Type[] types)
        {
            var objTypes = types
                .Select(x => typeof(PvfObjectBase).IsAssignableFrom(x) && !x.IsAbstract ? x : null)
                .Where(x => x != null);

            foreach (var type in objTypes)
            {
                BuildPvfObject(type);
            }
        }

        private void BuildModules(Type[] types)
        {
            var objTypes = types
                .Select(x =>
                    typeof(IPvfModule).IsAssignableFrom(x) && !x.IsAbstract &&
                    x.GetCustomAttribute<ModuleEntryAttribute>() != null
                        ? x
                        : null)
                .Where(x => x != null);

            foreach (var objType in objTypes)
            {
                LoadModule(objType, out _);
            }
        }

        public void Configure(Assembly assembly)
        {
            var types = assembly.GetTypes();
            BuildModules(types);
            BuildObjectLayouts(types);
        }
    }
}